async-config-poll: add httpPoll (long-poll) scenario#147
Merged
Conversation
Extends the sample so it can exercise Keploy's httpPoll async-egress lane type (keploy#4368), not just the periodic poller: - config-stub: POLL_HOLD_SECONDS (default 0) holds a watch=true request open that long before answering — a server-timeout long-poll. Default 0 keeps the current immediate behavior for the periodic scenario. - ConfigWatchService: WATCH_ONCE (default off) opens exactly one watch poll then stops the daemon, so the recording holds a single long poll rather than a stream. Default off keeps the current infinite poller. - keploy-httppoll.yml: same lane as keploy.yml but type: httpPoll, so the poll is recorded as kind:HttpPoll with an open-duration (pollDurationMs) and held until its resolve testcase at replay. All additions are env-gated and backward compatible; the default run is unchanged. Signed-off-by: Aditya Sharma <aditya282003@gmail.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Aditya-eddy
added a commit
to keploy/keploy
that referenced
this pull request
Jul 16, 2026
Adds a scenario: [periodic, httppoll] axis to the async_config_poll job so the async-egress e2e validates the new httpPoll long-poll lane type, not just the periodic poller: - periodic (unchanged): fast poller, stub answers immediately, lane type http -> asserts the engine SERVED the polls (served >= 1, no drift). - httppoll: one WATCH_ONCE poll held open by the stub (POLL_HOLD_SECONDS), lane type httpPoll (keploy-httppoll.yml) -> asserts the poll is recorded as kind:HttpPoll with a pollDurationMs, and that at replay the engine HELD it until its resolve testcase (held >= 1). The >60s hang-watchdog exemption is unit-tested (supervisor); this e2e uses a short hold for speed. TEMP: the samples-java checkout is pinned to the fork branch (Aditya-eddy/samples-java async-config-poll-httppoll) because the sample changes are in keploy/samples-java#147, not yet on main. REVERT the checkout to keploy/samples-java (main) once #147 merges. Signed-off-by: Aditya Sharma <aditya282003@gmail.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
praagyajain
approved these changes
Jul 16, 2026
There was a problem hiding this comment.
Pull request overview
Extends the async-config-poll sample with an env-gated “httpPoll / long-poll” scenario so Keploy can record/replay the config-watch egress as an HttpPoll (capturing pollDurationMs) in addition to the existing periodic polling behavior.
Changes:
- Add
WATCH_ONCEgating inConfigWatchServiceto run a single watch poll for the long-poll scenario. - Add
POLL_HOLD_SECONDStoconfig-stubto holdwatch=truerequests open to simulate a server-timeout long-poll. - Add a separate
keploy-httppoll.ymlconfig that mirrorskeploy.ymlbut sets the async lane type tohttpPoll.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| async-config-poll/src/main/java/com/example/asyncconfig/config/ConfigWatchService.java | Adds WATCH_ONCE behavior to stop after a single watch poll for the httpPoll scenario. |
| async-config-poll/keploy-httppoll.yml | Introduces a Keploy config variant using type: httpPoll for the config-watch async lane. |
| async-config-poll/config-stub/main.go | Adds a configurable long-poll hold (POLL_HOLD_SECONDS) for watch=true requests. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+96
to
+98
| if (watchOnce) { | ||
| break; // single long-poll connection for the httpPoll scenario | ||
| } |
Comment on lines
+48
to
+56
| if hold > 0 { | ||
| // Long-poll: hold the connection open until the server timeout, | ||
| // then deliver the next version. Abort if the client disconnects. | ||
| select { | ||
| case <-time.After(hold): | ||
| case <-r.Context().Done(): | ||
| return | ||
| } | ||
| } |
Aditya-eddy
added a commit
to keploy/keploy
that referenced
this pull request
Jul 17, 2026
keploy/samples-java#147 (the async-config-poll httpPoll sample hooks) is merged to main, so the async_config_poll job no longer needs the temporary fork-branch pin. Restore the checkout to keploy/samples-java (main). Signed-off-by: Aditya Sharma <aditya282003@gmail.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Aditya-eddy
added a commit
to keploy/keploy
that referenced
this pull request
Jul 17, 2026
…s) (#4368) * docs: design spec for async HTTP long-poll testcases Slice 1 of the async-testcase effort: egress HTTP long-poll consumers. Captures the recording-order-anchored interleave replay model, the config-lane classifier, per-delivery artifact + side-effect assertion, and the serialize-across-lanes concurrency decision. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Aditya Sharma <aditya282003@gmail.com> * docs(async): general async-egress engine design + implementation plan Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Aditya Sharma <aditya282003@gmail.com> * feat(async): add AsyncLane model + config.Async section Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Aditya Sharma <aditya282003@gmail.com> * feat(async): add AsyncParser + AsyncAware capability interfaces Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Aditya Sharma <aditya282003@gmail.com> * feat(async): transport-agnostic engine (gated ordered serving + shape verdict) Adds the real Engine implementation: per-lane ordered mock streams built from async-tagged mocks (sorted by asyncSeq), gated serving via a completed-testcase counter compared against each mock's anchorPos (anchorPos=0 at startup is always armed), request-shape verdicts delegated to the lane's AsyncParser, and keep-alive fallback when no mock is armed. Removes the Task 2 temporary `type Engine struct{}` placeholder from async.go now that the real type lives in engine.go. Engine depends only on *models.Mock, models.AsyncLane, and the AsyncParser interface (no transport imports), proven by a pluggability test that drives the same engine with a second, independent fake parser/lane with zero engine changes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Aditya Sharma <aditya282003@gmail.com> * fix(async): deterministic lane priority + idempotent Load + nil-parser warning Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Aditya Sharma <aditya282003@gmail.com> * feat(async): HTTP AsyncParser (lane glob, 204 keep-alive, shape via SchemaMatch) Implements async.AsyncParser + async.AsyncAware on *HTTP: - MatchesLane: host/path glob (path.Match) against lane.Match. - EmptyResponse: minimal 204 No Content keep-alive. - MatchRequestShape: reuses the existing SchemaMatch matcher against the single recorded mock. Volatile query params (lane.VolatileParams) are stripped from BOTH the live and recorded request before comparison — stripping only the recorded side (as a naive reading of the shape would suggest) leaves the query key-set counts unequal in SchemaMatch's MapsHaveSameKeys check and spuriously fails the match. Adds asyncEngine *async.Engine field + SetAsyncEngine setter to HTTP struct. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Aditya Sharma <aditya282003@gmail.com> * test(async): cover recorded-side URLParams volatile strip in MatchRequestShape Add TestMatchRequestShapeStripsRecordedURLParams and its negative companion TestMatchRequestShapeRecordedURLParamsKeyDrift, which build a recorded mock directly with a populated Spec.HTTPReq.URLParams (unlike httpMock(), which always leaves URLParams nil). This exercises stripVolatile's recorded-side `if req.URLParams != nil` branch for the first time, mirroring what pkg.URLParams(req) populates at record time in production. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Aditya Sharma <aditya282003@gmail.com> * feat(async): record hook stamps async metadata on lane egress Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Aditya Sharma <aditya282003@gmail.com> * fix(async): order-independent anchor tie-break + record-time nil-parser warning Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Aditya Sharma <aditya282003@gmail.com> * feat(async): plumb engine onto Proxy (construct + inject + per-test position advance) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Aditya Sharma <aditya282003@gmail.com> * feat(async): load complete async corpus at agent StoreMocks seam Add AsyncMockLoader optional Proxy capability and wire the agent to hand the async engine the COMPLETE async-mock corpus exactly once per store path (Engine.Load is run-once). In StoreMocksStream, async mocks are collected DURING the decode loop (before disk/resident routing) so per-test async egress parked on disk under strict mock-window is still loaded; in StoreMocks everything is resident so we collect from params. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Aditya Sharma <aditya282003@gmail.com> * feat(async): serve async deliveries + keep-alive in HTTP decode path Extract the matched-mock response serializer into (*HTTP).buildMockResponseBytes and reuse it in both the existing matched path and a new async serving branch in decodeHTTP. When a live outbound request routes to a configured async lane, the transport-agnostic engine decides what to serve (recorded delivery once armed, or a keep-alive payload) instead of normal mock matching. The branch mirrors the telemetry short-circuit: write, read the next poll, continue, using the errCh <- err / return + ctx.Err() early-return pattern. Adds liveReqToMock to bridge the matcher's req into a *models.Mock for the engine. Extraction is behavior-identical; full http suite still passes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Aditya Sharma <aditya282003@gmail.com> * docs(async): correct Task 6 plan (load at agent StoreMocks seam, split 6a/6b/6c) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Aditya Sharma <aditya282003@gmail.com> * feat(async): install AsyncRecorder record hook when lanes configured Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Aditya Sharma <aditya282003@gmail.com> * fix(async): stamp in Before* record hooks so metadata persists AfterMockInsert fires after mockDB.InsertMock persists the mock, so the async metadata stamp never reached the recorded YAML. Move stamping to BeforeMockInsert (mirrors the enterprise obfuscator) and track testcase windows in AfterTestCaseInsert (TestCase.Name is assigned by InsertTestCase). Caught by the end-to-end record run against a real long-poll sample app. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Aditya Sharma <aditya282003@gmail.com> * docs(async): record Before-hook fix + e2e findings (timing, verdict visibility) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Aditya Sharma <aditya282003@gmail.com> * feat(async): surface async verdict at replay wind-down Add Engine.LogReport (fires once via sync.Once) and call it from SetGracefulShutdown and StopProxyServer so the async verdict (served / shape_flags / not_exercised) prints at end of replay, with one WARN per shape drift. Makes the shape-flag verdict user-visible; verified e2e (mutating a served mock's request method surfaces shape_flags:1 + WARN while still serving, sync tests still pass). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Aditya Sharma <aditya282003@gmail.com> * refactor(async): apply cleanup-review findings - engine: run shape-match outside the mutex (Decide no longer holds the lock across the parser call); drop the parallel lanes map in favor of the ordered slice; cache seq/anchorPos as ints at Load instead of re-parsing per poll; own the first-window rule in Engine.AdvanceWindow (removes Proxy.asyncWindowSeen). - http: build the volatile-param set once per shape check; move the shared buildMockResponseBytes serializer to decode.go (out of the async-only file); reuse flakyHeaderNoise() in decode.go's auto-noise setup; flatten the async serving branch's triplicated error handling into one closure. - agent: skip the async-tag scan entirely when the proxy is not an AsyncMockLoader. Behavior-preserving; unit suites + e2e verdict unchanged (served:1, not_exercised:11). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Aditya Sharma <aditya282003@gmail.com> * feat(async): query + regex lane matching (matchQuery, pathRegex) Async lanes can now target egress more precisely on the same path: - matchQuery: require query key=value (e.g. watch=true) so a long-poll variant of an endpoint is async while a same-path boot call (watch=false) is not. - match.pathRegex: a regex alternative to the glob match.path, for URLs a glob can't express; reuses pkg/matcher.MatchesAnyRegex (cached, fail-closed). queryOf follows the package's URLParams-first precedence; MatchesLane uses a single matched flag instead of restating the configured-criteria list. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Aditya Sharma <aditya282003@gmail.com> * refactor(async): address PR review — log levels + capability registration Apply Copilot review feedback on the async-egress engine: - proxy.InitIntegrations: register an AsyncParser with the engine even when the integration does not implement AsyncAware. Registration was nested inside the AsyncAware check, so an AsyncParser-only integration would silently never register for routing (LaneFor/Decide). - engine.Decide: no-parser branch WARN -> DEBUG with a config hint. The branch is defensive/near-unreachable (LaneFor only matches when a parser exists), so a WARN there is misleading noise. - engine.LogReport: per-flag shape-drift WARN -> INFO with a remediation hint (re-record, or widen the lane's volatileParams / match). Matches the Info-level verdict header for one coherent end-of-run summary. - record.BeforeMockInsert: per-mock unresolved-parser WARN -> DEBUG. The root cause is already reported once, loudly, at startup; keeping the per-mock repeat at DEBUG avoids flooding real recordings. - record.ResolveAsyncParsers: unregistered-type and not-an-AsyncParser WARN -> ERROR with actionable hints. These are genuine config errors that silently defeat intent (mocks never stamped async). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Aditya Sharma <aditya282003@gmail.com> * feat(async): auto-generate lane names from routing identity AsyncLane.Name is now optional. When omitted, EffectiveName derives a deterministic name from the lane's ROUTING identity (type + match + matchQuery) — stable across record and replay, so it works as the join key stamped on mocks (MetaAsyncLane) and re-derived by the replay engine. Caller-supplied names are still honored verbatim. - models.AsyncLane: Name is now omitempty; add EffectiveName() and the bulk WithEffectiveNames(). The derived name excludes volatileParams/notExercised (replay-time tuning a user may change between runs, which must not shift the join key). A path-derived slug keeps it readable (e.g. http-poll-87288cbd). - NewAsyncRecorder and NewEngine normalize their lanes through WithEffectiveNames, so the record-time stamp and the replay-time lookup use the same key. WithEffectiveNames also returns a fresh copy, subsuming the manual defensive copy NewEngine used to make. Signed-off-by: Aditya Sharma <aditya282003@gmail.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci(async): add async-config-poll e2e job to java_linux Add a Java-on-Linux CI job that exercises the async-egress engine end to end against the async-config-poll sample (keploy/samples-java): record the /health and /rules HTTP tests plus the MySQL + config-service egress, then replay with the real deps down and assert both the ingress tests PASSED and the engine's `async egress verdict` (served >= 1, shape_flags == 0 — the volatile "version" watch param matches cleanly). - New reusable-workflow job `async_config_poll` in java_linux.yml, modelled on the existing tidb/mysql jobs (same guard, download-binary, checkout samples-java, upload artifacts). Deviations, each commented: Temurin 8 (the sample is Spring Boot 1.5), an added setup-go step for the config stub, and a single build/build matrix cell — the released binary can neither stamp nor serve async mocks, so the *_latest cells could never validate the feature (mirrors tidb-stmt-cache's omission comment). - New run script test_workflow_scripts/java/async_config_poll/java-linux.sh: brings up MySQL (docker compose) with a TCP readiness probe, builds+runs the Go config stub, records, then replays and checks the report + async verdict. Deliberately does not source update-java.sh (which pins Java 17). The sample app lives in keploy/samples-java#146; this job goes green once that merges. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Aditya Sharma <aditya282003@gmail.com> * docs(async): drop internal design/plan docs from the repo Remove the superpowers-generated design specs and implementation plan; these are internal working artifacts and do not belong in keploy/keploy. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Aditya Sharma <aditya282003@gmail.com> * fix(async): keep async-egress mocks out of the per-test mock mapping The test↔mock mapping is binned by the agent's SyncMockManager purely on timestamp-window overlap; it has no async awareness (lanes are known only in the record service, where the AsyncRecorder hook stamps MetaAsync). So a background/long-poll egress whose timestamp happened to fall inside a testcase's request window was being attributed to that testcase's mock set — even though async mocks are served at replay by the async engine from the complete corpus and must never be a per-test mock. Track the tempIDs the hook stamps MetaAsync (asyncMockIDs) and exclude them when building each test's mapping. The mock-insert goroutine records the async marker before publishing the tempID to correlationMap, and the mapping goroutine reads it only after finding the tempID there, so a resolved tempID always has its async status decided (single writer, single reader, unique tempIDs — no race). Extracted the mapping resolution into Recorder.resolveMappingEntries and unit- tested that async tempIDs overlapping a window are excluded while ordinary (sync) egress is still mapped. Signed-off-by: Aditya Sharma <aditya282003@gmail.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(async): drop unenforced notExercised knob, add lane diagnostics The notExercised (skip|fail) lane field was documented and settable but its value was never read by any code path, so `fail` silently behaved as `skip` — a misleading knob on a new public config surface. Async polling is keploy-managed and timing-dependent, so the not-exercised policy is keploy's to decide, not a user knob: remove the field. The engine still tallies and logs a not-exercised count (report-only, non-blocking). Also address two diagnostics raised in review: - NewEngine logs each configured lane's resolved match criteria at startup, so an over-broad glob (which would hijack ordinary sync egress) is visible. - SetMocks (the non-windowed fallback path, which never advances the async window) warns once that only startup-anchored deliveries will arm. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Aditya Sharma <aditya282003@gmail.com> * feat(async): AsyncLane.IsPoll/BaseType + poll metadata keys Signed-off-by: Aditya Sharma <aditya282003@gmail.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(async): HttpPoll kind + pollKind registry Signed-off-by: Aditya Sharma <aditya282003@gmail.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(async): HttpPoll encode/decode reuses the HTTP wire format Signed-off-by: Aditya Sharma <aditya282003@gmail.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(async): register Http->HttpPoll poll kind in the http integration Signed-off-by: Aditya Sharma <aditya282003@gmail.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(async): stamp poll metadata + re-kind on poll lanes; resolve parser by base type Signed-off-by: Aditya Sharma <aditya282003@gmail.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(async): hold poll deliveries until their resolve testcase (sync.Cond) Adds asyncEntry.poll (from MetaAsyncPoll), a sync.Cond on the engine mutex broadcast from AdvanceWindow/OnTestComplete, and a ctx-aware Decide(ctx, lane, live) that parks a not-yet-armed POLL entry on the cond until completed reaches its anchorPos or ctx is done (cond.Wait releases the lock while parked, so AdvanceWindow is never gated by an outstanding long-poll). Non-poll deliveries keep the pre-existing immediate keep-alive behavior. Adds a held counter surfaced via ReportSnapshot.Held and LogReport. Extracts the shape-match/verdict logic into a decideServe helper and the "no data yet" path into a nil-safe keepAlive helper, both reused by Decide. Resolves parsers by lane.BaseType() in both LaneFor and Decide (not raw lane.Type), so a "httpPoll" lane routes to the parser registered under "http" — without this a poll lane's live request never matches in LaneFor and the hold could never engage. Also fixes the one other pre-existing caller of the old Decide(lane, live) signature (pkg/agent/proxy/proxy_async_test.go, predating this change) to pass context.Background(), so `go test ./...` stays green repo-wide. Signed-off-by: Aditya Sharma <aditya282003@gmail.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(async): re-peek cursor entry on every Decide wake, not just once A parked poll's cond.Wait woke, then Decide re-read s.mocks[s.cursor] and served/advanced it without re-checking that entry's own anchorPos. Under concurrent same-lane Decide calls, another consumer could advance the cursor while this one was parked, so the waking call would serve a later, not-yet-armed delivery. Restructure the hold as a re-peek loop under the lock: every iteration (initial pass and every wake) re-reads the current cursor entry and re-validates completed >= entry.anchorPos before serving, closing the gap the pre-Task-6 code never had. Add TestDecideConcurrentSameLaneServesInOrderByAnchor: two poll deliveries (anchorPos 1 and 2) on one lane, two concurrent Decide calls, asserting each resolves only at its own anchor and never early. Signed-off-by: Aditya Sharma <aditya282003@gmail.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: address final whole-branch review findings for httpPoll feature - gofmt pkg/models/async.go (MetaPollDurationMs const-block alignment, CI lint blocker) - engine.go Decide: read e.parsers[lane.BaseType()] under e.mu.Lock() instead of before it, closing a latent data race with RegisterParser (no behavior change) - diskmocks.go EligibleForResponseSpill: include models.HttpPoll alongside models.HTTP so poll response bodies are disk-spill eligible, mirroring mockdb/util.go's four switches - asynchook.go: fix stale ResolveAsyncParsers doc comment (keyed by BaseType(), not Type); note BaseType()'s case-sensitive keying vs IsPoll()'s case-insensitive suffix check - asynchook_test.go: add TestPollLaneClampsNegativePollDurationToZero and TestAsyncPollMockExcludedFromPerTestMapping, locking the pollDurationMs clamp and the per-test mapping exclusion invariants (both verified via temporary code removal to ensure they actually fail on regression) Signed-off-by: Aditya Sharma <aditya282003@gmail.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(async): exempt poll-lane connections from the record hang watchdog A long-poll egress connection makes no byte progress for far longer than the supervisor's 60s hang budget while the server holds it open. The watchdog could not tell that apart from a hung parser, so it aborted the parser and fell through to passthrough before the delivery arrived — the app still got its response but the poll's mock was never recorded (30s holds captured, 300s holds silently dropped). recordV2 now matches each request against the configured async lanes by request shape (LaneFor) as soon as the request is parsed, and for a poll-type lane calls the new Session.SuspendWatchdog / Supervisor. SuspendWatchdog to permanently disarm hang detection on that connection. Panic and mem-cap protection stay active; only no-progress hang detection is disabled, and only for connections whose in-flight request routes to a poll lane. Signed-off-by: Aditya Sharma <aditya282003@gmail.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(async): address review of poll-lane watchdog exemption - Latch the suspend once per connection so a keep-alive poll-poll-poll connection does not re-parse/re-match every request after the first. - Document that SuspendWatchdog disarms hang detection for the whole connection, not just the in-flight request (bounded keep-alive trade-off). - Add tests: a suspended supervisor still aborts on a mem-cap breach (suspend must not weaken mem-cap protection); malformed request bytes return false without panicking. Signed-off-by: Aditya Sharma <aditya282003@gmail.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(async): /simplify pass on httpPoll — reuse + leaner Decide - isPollLaneRequest reuses liveReqToMock (the same request->mock shaping the replay LaneFor path uses) instead of hand-building a mock; drops the manual URLParams loop that truncated multi-valued queries, and gates on a new Engine.HasPollLanes() so deployments with no poll lanes skip the re-parse. - Decide registers the ctx-done wake bridge lazily via context.AfterFunc only before it actually parks on cond.Wait, instead of spawning a goroutine + channel on every call (immediate-return paths no longer pay for it). - Drop decideServe's unused context parameter. Signed-off-by: Aditya Sharma <aditya282003@gmail.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci(async): cover the httpPoll scenario in async_config_poll e2e Adds a scenario: [periodic, httppoll] axis to the async_config_poll job so the async-egress e2e validates the new httpPoll long-poll lane type, not just the periodic poller: - periodic (unchanged): fast poller, stub answers immediately, lane type http -> asserts the engine SERVED the polls (served >= 1, no drift). - httppoll: one WATCH_ONCE poll held open by the stub (POLL_HOLD_SECONDS), lane type httpPoll (keploy-httppoll.yml) -> asserts the poll is recorded as kind:HttpPoll with a pollDurationMs, and that at replay the engine HELD it until its resolve testcase (held >= 1). The >60s hang-watchdog exemption is unit-tested (supervisor); this e2e uses a short hold for speed. TEMP: the samples-java checkout is pinned to the fork branch (Aditya-eddy/samples-java async-config-poll-httppoll) because the sample changes are in keploy/samples-java#147, not yet on main. REVERT the checkout to keploy/samples-java (main) once #147 merges. Signed-off-by: Aditya Sharma <aditya282003@gmail.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci(async): unpin samples-java checkout now that #147 is merged keploy/samples-java#147 (the async-config-poll httpPoll sample hooks) is merged to main, so the async_config_poll job no longer needs the temporary fork-branch pin. Restore the checkout to keploy/samples-java (main). Signed-off-by: Aditya Sharma <aditya282003@gmail.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(async): move async bookkeeping to a typed top-level block Async-egress data (lane, order, anchor, poll/duration) was stamped into the owning parser's flat Spec.Metadata, and poll deliveries were re-kinded to a cosmetic HttpPoll kind. Both mixed the async engine's concern into the parser's model and taxed every Kind switch. Introduce models.AsyncMeta and carry it in its own block — Spec.Async in memory, a kind-agnostic top-level `async:` block on the recorded doc. Its presence marks a mock as async (replacing the MetaAsync flag); Poll marks a held long-poll (replacing the HttpPoll kind + pollKind registry). Fields are typed (seq/anchorPos/pollDurationMs ints, poll bool). A poll mock keeps its base kind (Http). Following keploy's PostgresV3 precedent (sub-type in Spec, not Kind). Removed: HttpPoll kind, MetaAsync* string consts, RegisterPollKind/PollKindFor. AsyncLane.IsPoll()/BaseType() stay (they describe the config lane type). Round-trip test (TestAsyncBlockRoundTrips) proves the block survives encode->decode; the async keys no longer leak into Spec.Metadata. Signed-off-by: Aditya Sharma <aditya282003@gmail.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(async): /simplify pass on the async-block refactor - Carry the Async envelope field across the two codec.go format converters (DocToJSON, jsonDocToYamlDoc) and clone it in Mock.DeepCopy — the async field was added to the doc structs but these paths were missed (mirrors how Noise is handled). - Add Mock.IsAsync() and route the 5 duplicated `Spec.Async != nil` checks (engine Load, agent collect x2, record mapping) through it (matches the HasSpilledResponse predicate precedent). - ci: the httppoll async_config_poll assertion checked `kind: HttpPoll`, which no longer exists — poll mocks stay kind Http, so assert the async block's `poll: true` + pollDurationMs instead. - Rename the local `async` var (shadowed the async package import), fix comments referencing removed MetaAsync*/HttpPoll symbols, and add a DeepCopy async-isolation test. Signed-off-by: Aditya Sharma <aditya282003@gmail.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * review(async): address PR Pilot (opus/high) findings - engine.go LaneFor: read e.parsers under e.mu, mirroring Decide's deliberate lock (commit 9dff3ad) so every e.parsers access is guarded — LaneFor is on both the replay decode and record isPollLaneRequest paths. - Lock the JSON storage-format round-trip: TestAsyncBlockRoundTrips now also serializes via MarshalDoc(FormatJSON)/UnmarshalDoc and asserts Spec.Async survives DocToJSON + jsonDocToYamlDoc (two converters that were originally missed), not just the yaml.Node path. - WarnNonWindowed: note that a testcase-anchored POLL delivery blocks its connection until replay teardown on the non-windowed path. - Fix stale collectAsyncMocks doc comment (Spec.Metadata → Mock.IsAsync). Signed-off-by: Aditya Sharma <aditya282003@gmail.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Signed-off-by: Aditya Sharma <aditya282003@gmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Extends the existing async-config-poll sample so it can exercise Keploy's
httpPollasync-egress lane type (keploy/keploy#4368), in addition to the current periodic-poller scenario.POLL_HOLD_SECONDS(default0) holds awatch=truerequest open that long before answering — a server-timeout long-poll. Default0preserves the current immediate behavior.WATCH_ONCE(default off) opens exactly one watch poll then stops the daemon, so the recording holds a single long poll rather than a stream. Default off preserves the current infinite poller.keploy.ymlbuttype: httpPoll, so the poll is recorded askind: HttpPollwith an open-duration (pollDurationMs) and held until its resolve testcase at replay.All additions are env-gated and backward compatible — the default run is unchanged, so the existing periodic scenario keeps working.
Why
The keploy
async_config_pollCI job (in keploy/keploy) is gaining ascenario: [periodic, httppoll]matrix so it validates the newhttpPolllong-poll path end-to-end (record →kind: HttpPoll+pollDurationMs→ replayheld ≥ 1). That job needs these sample hooks. The matching keploy/keploy change is on PR #4368.Validated
Locally against this sample (jdk8 + MySQL + config-stub, keploy built from keploy#4368):
1 kind: HttpPoll,pollDurationMs: "5027"(~5s hold), single poll.test exit 0, all tests PASSED, async egress verdict{served: 1, held: 1, shape_flags: 0}.🤖 Generated with Claude Code
Signed-off-by: Aditya Sharma aditya282003@gmail.com